/*
 * Copyright (c) 2009 John Pritchard, WTKX Project Group
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package wtkx.io;

/**
 * <p> Fast hexidecimal numeric coding correct (as to recode itself)
 * across all integer and long values.  Output A-F characters are
 * lower case. </p>
 * 
 * @author jdp
 * @since 1.1
 */
public class Hex 
    extends Bits
{

    private final static char[] chars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

    /**
     * From ASCII to binary
     */
    public final static byte[] Decode ( String h){
        if (null != h){
            int len = h.length();
            byte[] ascii = new byte[len];
            h.getBytes(0,len,ascii,0);
            return Decode( ascii, 0, len);
        }
        else
            return null;
    }
    /**
     * From ASCII to binary
     */
    public final static byte[] Decode ( byte[] b){
        if (null != b)
            return Decode( b, 0, b.length);
        else
            return null;
    }
    /**
     * From ASCII to binary
     */
    public final static byte[] Decode ( byte[] cary, int ofs, int len){
        int olen = len;
        int term = len-1;

        if ( 1 == (len&1)){  //(odd number of digits in `cary')

            len += 1;

            term = len-1;
        }

        byte[] buffer = new byte[len/2];

        int bcm = 1;

        if ( 0 < ofs){

            if (1 == (ofs&1))

                bcm = 2;
            else
                bcm = 1; 

            len += ofs;
        }

        byte ch;

        for ( int cc = ofs, bc = 0; cc < len; cc++){

            if ( cc == term && cc == olen)

                break; //(odd number of digits in `cary')
            else 
                ch = cary[cc];

            if ( ofs < cc && (0 == (cc & bcm)))
                bc += 1;

            if ( '0' <= ch){

                if ( '9' >= ch){

                    if ( 1 == (cc&1))

                        buffer[bc] += (byte)(ch-'0');
                    else
                        buffer[bc] += (byte)((ch-'0')<<4);
                }
                else if ( 'a' <= ch && 'f' >= ch){

                    if ( 1 == (cc&1))

                        buffer[bc] += (byte)((ch-'a')+10);
                    else
                        buffer[bc] += (byte)(((ch-'a')+10)<<4);
                }
                else if ( 'A' <= ch && 'F' >= ch){

                    if ( 1 == (cc&1))

                        buffer[bc] += (byte)((ch-'A')+10);
                    else
                        buffer[bc] += (byte)(((ch-'A')+10)<<4);
                }
                else
                    throw new IllegalArgumentException("String '"+new String(cary,0,ofs,olen)+"' is not hex.");
            }
            else
                throw new IllegalArgumentException("String '"+new String(cary,0,ofs,olen)+"' is not hex.");

        }
        return buffer;
    }
    /**
     * <p> Basic HEX encoding primitives. </p>
     * @param val An eight bit value.
     * @return Low four bits encoded to a hex character.
     */
    public final static char Encode8Low(int val){
        return chars[val & 0xf];
    }
    /**
     * <p> Basic HEX encoding primitives. </p>
     * @param val An eight bit value.
     * @return High four bits encoded to a hex character.
     */
    public final static char Encode8High(int val){
        return chars[(val >>> 4) & 0xf];
    }

    public final static String Encode(byte ch){
        char[] string = new char[]{
            Encode8High(ch),
            Encode8Low(ch)
        };
        return new String(string);
    }

    public final static String Encode(java.math.BigInteger bint){
        if (null == bint)
            return null;
        else
            return Encode(bint.toByteArray());
    }
    /**
     * <p> binary to hexadecimal</p>
     */
    public final static String Encode ( byte[] buffer){
        if ( null == buffer)
            return null;
        else {
            int len = buffer.length;
            char[] cary = new char[(len*2)];
            int val, ca = 0;
            int leadingzero = 0;
            for ( int cc = 0; cc < len; cc++){

                val = (buffer[cc]&0xff);

                if (cc == leadingzero && 0 == val){
                    leadingzero += 1;
                }
                else {
                    cary[ca++] = (chars[(val>>>4)&0xf]);
                    cary[ca++] = (chars[ val&0xf]);
                }
            }
            if (0 < leadingzero){
                int trim = (leadingzero*2);
                int nlen = (cary.length - trim);
                if (0 < nlen){
                    char[] copier = new char[nlen];
                    System.arraycopy(cary,0,copier,0,nlen);
                    return new java.lang.String(copier);
                }
                else
                    return "00";//(consistent with value '1' encoded as '01')
            }
            else
                return new java.lang.String(cary);
        }
    }

    /**
     * <p> binary to hexadecimal</p>
     * @return Seven bit ASCII hexidecimal
     */
    public final static byte[] Encode2 ( byte[] buffer){
        if ( null == buffer)
            return null;
        else {
            int len = buffer.length;
            byte[] bary = new byte[(len*2)];
            int val, ca = 0;
            int leadingzero = 0;
            for ( int cc = 0; cc < len; cc++){

                val = (buffer[cc]&0xff);

                if (cc == leadingzero && 0 == val){
                    leadingzero += 1;
                }
                else {
                    bary[ca++] = (byte)(chars[(val>>>4)&0xf]);
                    bary[ca++] = (byte)(chars[ val&0xf]);
                }
            }
            if (0 < leadingzero){
                int trim = (leadingzero*2);
                int nlen = (bary.length - trim);
                if (0 < nlen){
                    byte[] copier = new byte[nlen];
                    System.arraycopy(bary,0,copier,0,nlen);
                    return copier;
                }
                else
                    return new byte[]{Zed};
            }
            else
                return bary;
        }
    }

    public final static String Encode( long value){
        byte[] bvalue = Long(value);
        return Encode(bvalue);
    }
    public final static String Encode( int value){
        byte[] bvalue = Integer(value);
        return Encode(bvalue);
    }
}
